package com.conversationboard.api;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.SecurityContext;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.conversationboard.api.formatters.XmlBoolean;
import com.conversationboard.api.formatters.XmlDateFormatter;
import com.conversationboard.api.xml.ApiXPath;
import com.conversationboard.api.xml.XmlUtils;
import com.conversationboard.cache.VoterCache;
import com.conversationboard.cache.readcounter.ReadCounterCache;
import com.conversationboard.controller.ipaddress.IPAddress;
import com.conversationboard.formatter.ImageHider;
import com.conversationboard.model.Board;
import com.conversationboard.model.Boards;
import com.conversationboard.model.Message;
import com.conversationboard.model.Messages;
import com.conversationboard.model.NoSuchThreadException;
import com.conversationboard.model.PollItem;
import com.conversationboard.model.PollResult;
import com.conversationboard.model.PollResults;
import com.conversationboard.model.Role;
import com.conversationboard.model.Thread;
import com.conversationboard.model.User;
import com.conversationboard.model.exceptions.MessageNotFoundException;

/* Send <Start>50</Start> to start at the fiftieth message. Send <Start>-1</Start> if you want to
 * see the most recent (pageSize) messages.
 * 
 * Send <Start>-2</Start> to start at the last read message. If there is no last read
 * message, it will start at the beginning. */

public class ThreadProcessor implements Processor {

	private static XPathExpression boardIdExpression;
	private static XPathExpression threadIdExpression;
	private static XPathExpression showImagesExpression;
	private static XPathExpression startExpression;
	private static XPathExpression pageSizeExpression;

	static {
		try {
			boardIdExpression = ApiXPath.getXPath().compile("/Requests/Thread/BoardId/text()");
			threadIdExpression = ApiXPath.getXPath().compile("/Requests/Thread/ThreadId/text()");
			showImagesExpression = ApiXPath.getXPath().compile("/Requests/Thread/ShowImages/text()");
			startExpression = ApiXPath.getXPath().compile("/Requests/Thread/Start/text()");
			pageSizeExpression = ApiXPath.getXPath().compile("/Requests/Thread/PageSize/text()");
		} catch (XPathExpressionException e) {
		}
	}


	@Override
	public String processXml(String xml, SecurityContext securityContext, HttpServletRequest request) throws ApiException {

		String response = "";

		try {

			Document document = XmlUtils.getDocument(xml);

			int boardId = Integer.parseInt((String) boardIdExpression.evaluate(document, XPathConstants.STRING));
			int threadId = Integer.parseInt((String) threadIdExpression.evaluate(document, XPathConstants.STRING));
			boolean showImages = ((String) showImagesExpression.evaluate(document, XPathConstants.STRING)).equalsIgnoreCase("Yes");
			int start = Integer.parseInt((String) startExpression.evaluate(document, XPathConstants.STRING));
			int pageSize = Integer.parseInt((String) pageSizeExpression.evaluate(document, XPathConstants.STRING));

			/* Count number of reads of threads */

			ReadCounterCache.getInstance().incrementThreadCounter(boardId, threadId, request.getHeader("Referer"));

			User user = User.get(request.getUserPrincipal());

			if (user.getRole() != Role.Anonymous) {
				user.setIpAddress(IPAddress.getIPAddress(request));
				user.setLastAccessed(new Date());
			}

			/* Update the user's last logged in time and login count if necessary */

			user.updateLastLoggedIn(IPAddress.getIPAddress(request));

			/* Don't let a user view a thread on a private board unless they are a member of that board. */

			Board board = Boards.getBoard(boardId);

			if (board.isPrivateBoard() && (!user.isMemberOfBoard(boardId))) {
				return "<Requests><Thread><Error>Not authorised</Error></Thread></Requests>";
			}

			Thread thread = Thread.get(boardId, threadId);
			boolean showDeleted = user.isBoardAdmin(boardId);

			response = this.deliverAsXml(user, request, board, thread, showDeleted, showImages, start, pageSize);

		} catch (SQLException e) {
			throw new ApiException(e);
		} catch (NoSuchThreadException e) {
			throw new ApiException(e);
		} catch (ParserConfigurationException e) {
			throw new ApiException(e);
		} catch (IOException e) {
			throw new ApiException(e);
		} catch (SAXException e) {
			throw new ApiException(e);
		} catch (XPathExpressionException e) {
			throw new ApiException(e);
		} catch (MessageNotFoundException e) {
			throw new ApiException(e);
		}

		return response;
	}


	private String deliverAsXml(User user, HttpServletRequest request, Board board, Thread thread, boolean returnDeleted, boolean showImages, int start, int pageSize) throws SQLException,
			MessageNotFoundException {

		StringBuilder buffer = new StringBuilder(thread.getMessageCount() * 50);
		int boardId = thread.getBoardId();
		int threadId = thread.getThreadId();

		/* Only return deleted if you've asked for it and you're an admin of this board */
		returnDeleted = (returnDeleted && user.isBoardAdmin(boardId));

		buffer.append("<Thread>");

		/* Thread Level Information */

		buffer.append("<BoardId>" + boardId + "</BoardId>");
		buffer.append("<ThreadId>" + threadId + "</ThreadId>");
		buffer.append("<BoardName>" + board.getName() + "</BoardName>");
		buffer.append("<MessageCount>" + thread.getMessageCount() + "</MessageCount>");

		String title = thread.getTitle();
		String creatorId = thread.getCreatorId();

		String createdOnString = XmlDateFormatter.format(thread.getTimestamp());
		String lastUpdatedString = XmlDateFormatter.format(thread.getLastUpdated());

		buffer.append("<Title><![CDATA[" + title + "]]></Title>");
		buffer.append("<CreatorId><![CDATA[" + creatorId + "]]></CreatorId>");
		buffer.append("<CreatedOn>" + createdOnString + "</CreatedOn>");
		buffer.append("<Key>" + request.getSession().getId() + "</Key>");
		buffer.append("<LastUpdated>" + lastUpdatedString + "</LastUpdated>");
		buffer.append("<Deleted>" + XmlBoolean.getBoolean(thread.isDeleted()) + "</Deleted>");
		buffer.append("<Worksafe>" + XmlBoolean.getBoolean(thread.isWorksafe()) + "</Worksafe>");
		buffer.append("<Locked>" + XmlBoolean.getBoolean(thread.isLocked()) + "</Locked>");
		buffer.append("<Poll>" + XmlBoolean.getBoolean(thread.isPoll()) + "</Poll>");

		if (thread.isPoll()) {
			buffer.append("<PollClosingDate>" + thread.getPollClosingDate() + "</PollClosingDate>");

			if (VoterCache.hasAlreadyVoted(user.getLoginId(), boardId, threadId)) {
				buffer.append("<Voted>Yes</Voted>");

				int optionId = VoterCache.userVotedFor(user.getLoginId(), boardId, threadId);
				buffer.append("<VotedFor>" + optionId + "</VotedFor>");
			} else {
				buffer.append("<Voted>No</Voted>");
			}
		}

		/* Poll Results */

		PollResults pollResults = thread.getPollResults();

		if (!(pollResults == null)) {

			List<PollResult> results = pollResults.getResults();

			buffer.append("<PollResults>");

			for (PollResult result : results) {
				buffer.append("<Result>");
				buffer.append("<Option><![CDATA[" + result.getOptionText() + "]]></Option>");
				buffer.append("<VoteCount>" + result.getVoteCount() + "</VoteCount>");
				buffer.append("</Result>");
			}

			buffer.append("</PollResults>");

		} else {

			if ((thread.isPoll()) && !thread.isPollClosed()) {

				List<PollItem> pollItems = thread.getPollItems();

				buffer.append("<PollOptions>");

				for (PollItem item : pollItems) {
					buffer.append("<Option>");
					buffer.append("<Id>" + item.getOptionId() + "</Id>");
					buffer.append("<Text><![CDATA[" + item.getOptionText() + "]]></Text>");
					buffer.append("</Option>");
				}

				buffer.append("</PollOptions>");
			}
		}

		/* Tack on the message ID the user has last read up to */

		int messageId = user.getMostRecentMessageId(boardId, threadId);

		if (messageId != 0) {
			buffer.append("<LastReadMessageId>" + messageId + "</LastReadMessageId>");
		}

		/* Reset the most recent message ID */

		int latestMessageId = thread.getLatestMessageId();
		user.setMostRecentMessageId(boardId, threadId, latestMessageId);

		/* Messages */

		buffer.append("<Messages>");

		Messages messages = thread.getMessages();

		/* If start = -1, then we want the most recent (pageSize) of messages - calculate that */

		if (start == -1) {
			if ((messages.size() - pageSize) >= 0) {
				start = messages.size() - pageSize;
			} else {
				start = 0;
			}
		}

		/* If start = -2, then we only want the messages from the point at which you last read */

		if (start == -2) {
			int readUpTo = user.getMostRecentMessageId(boardId, threadId);
			start = messages.getIndexFromMessageId(readUpTo);
		}

		/* Calculate and get the relevant page */

		int endMessage = start + pageSize;

		if (endMessage > messages.size()) {
			endMessage = messages.size();
		}

		if (start >= endMessage) {
			return "<Thread><Error>No such page</Error></Thread>";
		}

		for (int i = start; i < endMessage; i++) {

			Message message = messages.get(i);

			if (message.isDeleted() && (!returnDeleted)) {
				continue;
			}

			String timestampString = XmlDateFormatter.format(message.getTimestamp());

			buffer.append("<Message>");
			buffer.append("<MessageId>" + message.getMessageId() + "</MessageId>");
			buffer.append("<Deleted>" + XmlBoolean.getBoolean(message.isDeleted()) + "</Deleted>");
			buffer.append("<Timestamp>" + timestampString + "</Timestamp>");
			buffer.append("<DisplayName><![CDATA[" + message.getDisplayName() + "]]></DisplayName>");

			String body = message.getBody();

			/* If we're delivering to a mobile device, switch images for links to speed up loading */

			if (!showImages) {
				body = ImageHider.substituteImagesForLinks(message.getBody(), false, true, (!(user.getPreferences().isShowImages())));
			}

			buffer.append("<Body><![CDATA[" + body + "]]></Body>");
			buffer.append("</Message>");
		}

		buffer.append("</Messages>");
		buffer.append("</Thread>");

		String xmlData = buffer.toString();

		return xmlData;
	}

}
